#ifndef STUN_PROTOCOL_ATTR_H_
#define STUN_PROTOCOL_ATTR_H_

#include <arpa/inet.h>
#include <cstdint>
#include <memory>
#include <netinet/in.h>
#include <string>
#include <vector>

class StunAttr
{
public:
    enum EmAttrType
    {
        EmMappedAddress    = 0x0001,
        EmUserName         = 0x0006,
        EmMessageIntegrity = 0x0008,
        EmErrorCode        = 0x0009,
        EmRealm            = 0x0014,
        EmNonce            = 0x0015,
        EmXorMappedAddress = 0x0020,
        EmRequestTransport = 0x0019,
        EmFingerPrint      = 0x8028,
    };
    enum EmParseStatus
    {
        EmParseInit     = 0,
        EmParseNeedData = 1,
        EmParseSuccess  = 2,
        EmParseFailed   = 3,
    };
    static const int addr_length = 128;
    static const int padding_len = 4;

public:
    StunAttr();
    StunAttr(int type);
    StunAttr(int type, int length);
    virtual ~StunAttr();
    int                              AttrType() const;
    int                              AttrLength() const;
    int                              AttrPadding() const;
    uint32_t                         MagicCookie() const;
    uint8_t *                        TransactionID();
    void                             AttrLength(int length);
    void                             MagicCookie(uint32_t cookie);
    void                             AttrPadding(int padding);
    void                             TransactionID(uint8_t * data, int length);

    virtual int                      ParseBufferToAttr(const uint8_t * data, int data_length, int & use_length) = 0;
    virtual int                      ComposeAttrToBuffer(uint8_t * data, int data_length)                       = 0;
    virtual void                     DumpAttr();
    int                              ComposeBaseAttrToBuffer(uint8_t * data, int data_length) const;
    static std::shared_ptr<StunAttr> CreateStunAttr(int attr_type);

private:
    uint32_t             m_magic_cookie{};
    std::vector<uint8_t> m_transaction_id;
    uint16_t             m_attr_type;
    uint16_t             m_attr_length;
    uint16_t             m_attr_padding;
};

class StunMappedAddressAttr : public StunAttr
{
public:
    StunMappedAddressAttr();
    ~StunMappedAddressAttr() override;

    int         ParseBufferToAttr(const uint8_t * data, int data_length, int & use_length) override;
    int         ComposeAttrToBuffer(uint8_t * data, int data_length) override;
    void        DumpAttr() override;
    uint16_t    HostPort() const;
    std::string HostIP();
    void        AddressInfo(uint8_t family, uint16_t port, const std::string & ip);

private:
    struct sockaddr_in  m_address{};
    struct sockaddr_in6 m_address_v6{};
    uint8_t             m_family_type{};
};

class StunXorMappedAddressAttr : public StunAttr
{
public:
    StunXorMappedAddressAttr();
    ~StunXorMappedAddressAttr() override;
    int         ParseBufferToAttr(const uint8_t * data, int data_length, int & use_length) override;
    int         ComposeAttrToBuffer(uint8_t * data, int data_length) override;
    uint16_t    HostPort() const;
    std::string HostIP();
    void        DumpAttr() override;
    void        AddressInfo(uint8_t family, uint16_t port, const std::string & ip);

private:
    struct ::sockaddr_in  m_address{};
    struct ::sockaddr_in6 m_address_v6{};
    uint8_t               m_family{};
};

class StunRequestTransportAttr : public StunAttr
{
public:
    StunRequestTransportAttr();
    ~StunRequestTransportAttr() override;
    int  ParseBufferToAttr(const uint8_t * data, int data_length, int & use_length) override;
    int  ComposeAttrToBuffer(uint8_t * data, int data_length) override;
    void DumpAttr() override;
    void Transport(uint8_t type);
    int  Transport() const;

private:
    uint8_t m_transport_type{}; // udp:0x11,
};

class StunUserNameAttr : public StunAttr
{
public:
    StunUserNameAttr();
    ~StunUserNameAttr() override;
    int         ParseBufferToAttr(const uint8_t * data, int data_length, int & use_length) override;
    int         ComposeAttrToBuffer(uint8_t * data, int data_length) override;
    void        DumpAttr() override;
    std::string UserName();
    void        UserName(const std::string & user_name);

private:
    std::string m_username;
};

class StunFingerPrintAttr : public StunAttr
{
public:
    StunFingerPrintAttr();
    ~StunFingerPrintAttr() override;
    int      ParseBufferToAttr(const uint8_t * data, int data_length, int & use_length) override;
    int      ComposeAttrToBuffer(uint8_t * data, int data_length) override;
    void     DumpAttr() override;
    int      VerifyFingerPrint(const uint8_t * data, int length);
    uint32_t ComputeFingerPrint(const uint8_t * data, int length);
    uint32_t FingerPrint() const;

private:
    uint32_t m_crc32;
    uint32_t m_fingerprint;
};

#endif
